package com.example.socket.anno.impl;

import com.example.socket.anno.InBody;
import com.example.socket.codec.Coder;
import com.example.socket.codec.MessageConvertor;
import com.example.socket.core.Request;
import com.example.socket.core.Session;
import com.example.socket.exception.ParameterException;
import com.example.socket.parameter.Parameter;
import com.example.socket.parameter.ParameterKind;
import com.example.socket.parameter.ResultCallback;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * {@link InBody}注释的处理器
 * @author frank
 */
public class InBodyParameter implements Parameter {

    private static final Logger logger = LoggerFactory.getLogger(InBodyParameter.class);

    public static InBodyParameter valueOf(InBody annotation, Method method, int index, MessageConvertor convertor)
            throws Exception {
        String name = annotation.value();
        if (StringUtils.isEmpty(name)) {
            name = getParamaterName(method, index);
        }

        InBodyParameter result = new InBodyParameter();
        result.annotation = annotation;
        result.type = method.getGenericParameterTypes()[index];
        result.value = name;
        result.required = annotation.required();
        result.convertor = convertor;
        return result;
    }

    /** 获取参数名 */
    private static String getParamaterName(Method method, int index) throws Exception {
        Class<?> clazz = method.getDeclaringClass();
        if (clazz.isInterface()) {
            throw new ParameterException("接口方法的参数必须指定 InBody 注释的 value");
        }

        ClassPool pool = ClassPool.getDefault();
        CtClass clz = pool.get(clazz.getName());
        CtClass[] params = new CtClass[method.getParameterTypes().length];
        for (int i = 0; i < method.getParameterTypes().length; i++) {
            params[i] = pool.getCtClass(method.getParameterTypes()[i].getName());
        }
        CtMethod cm = clz.getDeclaredMethod(method.getName(), params);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        String name = attr.variableName(index + pos);
        return name;
    }

    private InBody annotation;
    private Type type;
    private String value;
    private boolean required;
    private MessageConvertor convertor;

    @Override
    public ParameterKind getKind() {
        return ParameterKind.IN_BODY;
    }

    @Override
    public Object getValue(Request<?> request, Session session, ResultCallback<?> callback) {
        Coder coder = convertor.getCoder((byte) 1);
        try {
            return coder.getInBody(request.getBody(), this);
        } catch (Exception e) {
            FormattingTuple message = MessageFormatter.format("命令[{}]请求的参数[{}]不存在", request.getCommand(), annotation);
            logger.warn(message.getMessage());
            throw new ParameterException(message.getMessage(), e);
        }
    }

    // Getter and Setter ...

    public Type getType() {
        return type;
    }

    public String getValue() {
        return value;
    }

    public boolean isRequired() {
        return required;
    }

    public InBody getAnnotation() {
        return annotation;
    }
}
